/* PS/2 Mouse driver for PC's and AT's.
 *
 * Written by Bergez, Fragulia & Wachnitz (22/11/2007)
 * 
 * 	Three button mouse
 */

#include <stdio.h>
#include "kernel.h"
#include <termios.h>
#include <signal.h>
#include <unistd.h>
#include <minix/callnr.h>
#include <minix/com.h>
#include "tty.h"

#define MOUSE		0x60	/* I/O port for keyboard data */
#define MS_STATUS	0x64	/* I/O port for status on AT */

#define ms_addr()	(&ms_lines[0])	/* there is only one mouse */


FORWARD _PROTOTYPE( int bit_neg, (int num));


/* Mouse structure, 1 per console */
struct ms_s {
	int icurrent_byte;
	int ibytes[3];
};


PRIVATE struct ms_s ms_lines[NR_CONS];

/*===========================================================================*
 *				ms_init					     *
 *===========================================================================*/
PUBLIC void ms_init(void)
{
/* Set mouse initial configuration and enable data reporting in stream mode */

  	struct ms_s *ms;

	ms = ms_addr();

	/* Sending write command (0x60) to the keyboard 
	   controller (port 64h)*/
	out_byte(MS_STATUS, 0x60);
	/* Sending write parameter to the keyboard controller (port 60h).
	   0x43 enables XLAT, INT2 and INT.
	   INT2 (Mouse Input Buffer Full Interrupt) - When set, IRQ 12 is 
	   generated when mouse data is available.
	   INT (Input Buffer Full Interrupt) - When set, IRQ 1 is generated 
	   when data is available in the input buffer.
	*/
	out_byte(MOUSE, 0x43);
	kb_wait();


	/* 0xD4 command (Write Mouse Device) - Sends parameter to the 
	   auxillary PS/2 device 
	*/
	out_byte(MS_STATUS, 0xD4);
	kb_wait();
	/* Changing sample rate */
	out_byte(MOUSE, 0xF3);
	kb_wait();
	/* 0xD4 command (Write Mouse Device) - Sends parameter to the 
	   auxillary PS/2 device 
	*/
	out_byte(MS_STATUS, 0xD4);
	kb_wait();
	/* Setting sample rate to 40 samples/sec */
	out_byte(MOUSE, 40);
	kb_wait();


	/* 0xD4 command (Write Mouse Device) - Sends parameter to the 
	   auxillary PS/2 device 
	*/
	out_byte(MS_STATUS, 0xD4);
	kb_wait();
	/* Changing resolution */
	out_byte(MOUSE, 0xE8);
	kb_wait();
	/* 0xD4 command (Write Mouse Device) - Sends parameter to the 
	   auxillary PS/2 device 
	*/
	out_byte(MS_STATUS, 0xD4);
	kb_wait();
	/* Setting resolution to 1:1 */
	out_byte(MOUSE, 0x00);
	kb_wait();


	/* 0xD4 command (Write Mouse Device) - Sends parameter to the 
	   auxillary PS/2 device 
	*/
	out_byte(MS_STATUS, 0xD4);
	kb_wait();
	/* In stream mode, 0xF4 "Enable Data Reporting" command */
	out_byte(MOUSE, 0xF4);
	kb_wait();

	/* Reading OBF (Output Buffer Full) bit to be OK */
	while((in_byte(MS_STATUS) & 1) == 0)
		;

	/* Set mouse initial values */
	ms->icurrent_byte = 0;
	ms->ibytes[0] = 0x08;
	ms->ibytes[1] = 0;
	ms->ibytes[2] = 0;

	/* Set mouse status initial values */ 
	mouse_status.left_button = 0;
	mouse_status.right_button = 0;
	mouse_status.middle_button = 0;
	mouse_status.x = 0;
	mouse_status.y = 0;

	/* Set mouse flag initial value */
	mouse_status.mouse_flag = 1;

	/* Set mouse interrupt handler */
	put_irq_handler(MOUSE_IRQ, ms_hw_int);	
	enable_irq(MOUSE_IRQ);		/* mouse initialised! */
}

/*===========================================================================*
 *				ms_hw_int				     *
 *===========================================================================*/
PUBLIC int ms_hw_int(irq)
int irq;
{
/* A mouse interrupt has occurred.  Process it. */
	
	struct ms_s *ms;
	int dx, dy;

	ms = ms_addr();

	/* Ignore first "acknowledge" (0xFA) bytes */
	if(mouse_status.mouse_flag)
	{
		while (in_byte(MOUSE) == 0xF4);
		mouse_status.mouse_flag = 0;
		ms->icurrent_byte= 3;
	}
	else
		ms->ibytes[ms->icurrent_byte++] = in_byte(MOUSE);

	/* When all bytes have been received, process them */ 
	if(ms->icurrent_byte == 3)
	{
		ms->icurrent_byte = 0;
		
		/* Checking which buttons have been pressed */
		mouse_status.left_button = ms->ibytes[0] & 0x01;
		mouse_status.right_button = (ms->ibytes[0] & 0x02) >> 1;
		mouse_status.middle_button = (ms->ibytes[0] & 0x04) >> 2;
		
		/* DEBUGGING AND TESTING */
		mouse_status.b1 = ms->ibytes[0];
		mouse_status.dx = ms->ibytes[1];
		mouse_status.dy = ms->ibytes[2];

		/* If relative movement on x is not zero and there is no overflow */ 
		if(ms->ibytes[1] != 0 && (ms->ibytes[0] & 0x40) == 0)
		{
			/* If movement was negative */
			if(ms->ibytes[0] & 0x10)
			{
				/* Converting value in base 2's complement */
				dx = bit_neg(ms->ibytes[1]) + 1;
				/* Updating x coordenate */
				mouse_status.x -= dx;
			}
			else
			{
				/* Updating x coordenate */
				mouse_status.x += ms->ibytes[1];
			}
		}

		/* If relative movement on y is not zero and there is no overflow */ 
		if(ms->ibytes[2] != 0 && (ms->ibytes[0] & 0x80) == 0)
		{
			/* If movement was negative */
			if(ms->ibytes[0] & 0x20)
			{
				/* Converting value in base 2's complement */
				dy = bit_neg(ms->ibytes[2]) + 1;
				/* Updating y coordenate */
				mouse_status.y += dy;
			}
			else
			{
				/* Updating y coordenate */
				mouse_status.y -= ms->ibytes[2];
			}
		}

		/* Setting mouse flag, indicating there is new information available */ 
		mouse_status.mouse_flag = 1;
	}

	force_timeout();
	return 1;
}

/*===========================================================================*
 *				bit_neg					     *
 *===========================================================================*/
PRIVATE int bit_neg(num)
int num;
{
/* Changes the status of all bits in the low byte */
/* This function helps doing the base 2's complement conversion */

	int i, n;
	int mask_array[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
	int neg_mask_array[] = {0xFE, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF, 0xBF, 0x7F};

	for (n = num, i = 0; i < 8; i++)
	{
		if (n & mask_array[i])
			n = n & neg_mask_array[i];
		else
			n = n | mask_array[i];
	}	
	
	return n;
}
